﻿/*
 * Tiff TiffBitmapDecoder
 * 
 * initial implementation : 20.10.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Windows.Media.Imaging;
using Petra.Gis.ImagingServices;
using Petra.Gis.ImagingServices.Tiff;

namespace Petra.Gis.ImagingServices
{
  ///<summary>
  /// codec that reads Tagged Image File Format (TIFF) image files.
  /// All know type of tag is recognized and all known color schemes is recognized
  /// all known data configuration is supported
  /// only some compression types are supported
  ///</summary>
  public class TiffBitmapDecoder : GridBitmapDecoderBase
  {

    protected TiffCodecInfo _tInfo;
    protected static Hashtable _compressions = new Hashtable();
    //private delegate Compression getCompression(TiffBitmapPage image);

    //private TiffBitmapPage[] _frames; //collection of subimages
    private TiffDataEndian _endian;   //instead of byteOrder
    //private long _nextIfdOffset;      //position of current ifd table
    private int _firstIfdOffset;      //position of first ifd table
    //private Stream _tiffStream;       //base data stream
    private TiffReader _tiffReader;
    //private BitmapCacheOption _cacheOption;
    //private BitmapCreateOptions _createOptions;

    
    #region constructors


    static TiffBitmapDecoder()
    {
      //register available compressions
      RegisterCompression(new CompressionDeflate());
      //registerDecoder(new TIFFDecoderModifiedHuffman());
      RegisterCompression(new TiffCompressionPackbits());
      RegisterCompression(new CompressionNone());
      RegisterCompression(new TiffCompressionLogLuv());
      RegisterCompression(new CompressionCCITT());
      RegisterCompression(new CompressionJpeg());
    }





    public TiffBitmapDecoder(Stream data, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption)
      : base(data, createOptions, cacheOption)
    {

      _tInfo = new TiffCodecInfo();

      //process()
      try
      {
        // parse whole tiff and create all necessary structs
        // but dont load any bitmap data
        readTiffImageFile(_data);
      }
      catch (IOException ioe)
      {
        _data.Close();
        throw new InvalidOperationException("I/O error occurred: " + ioe.ToString());
      }
    }






    public TiffBitmapDecoder(Uri FileUri, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption)
      : base(FileUri, createOptions, cacheOption)
    {
      _tInfo = new TiffCodecInfo();

      //process()
      try
      {
        // parse whole tiff and create all necessary structs
        // but dont load any bitmap data
        readTiffImageFile(_data);
      }
      catch (IOException ioe)
      {
        _data.Close();
        throw new InvalidOperationException("I/O error occurred: " + ioe.ToString());
      }

    }


    #endregion constructors


    #region public members


    //protected System.Collections.ObjectModel.ReadOnlyCollection<BlockBitmapSource> _frames;

    //public System.Collections.ObjectModel.ReadOnlyCollection<BlockBitmapSource> Frames
    //{
    //  get { return _frames; }
    //}

    protected System.Collections.ObjectModel.ReadOnlyCollection<BitmapFrame> _frames;

    public override System.Collections.ObjectModel.ReadOnlyCollection<BitmapFrame> Frames
    {
      get { return _frames; }
    }





    internal static Compression GetCompression(CompressionType reqCompressType)
    {
      Compression dataCompression;

      try
      {
        dataCompression = (Compression)_compressions[reqCompressType];
      }
      catch
      {
        throw new ArgumentException("Could not create decoder for this compression type: " +
          reqCompressType.ToString());
      }

      // setup active image to compression here ??
      //dataCompression.BitmapFrame = currentImage;

      return dataCompression;

    }






    ///<summary>
    /// Register a Compression class.
    /// TIFF knows many compression types, and this codec only supports some of them.
    /// To register an external Compression class with TIFFCodec, call this method
    /// with the class field of your decoder.
    /// As an example, for your TIFFDecoderMyCompression class,
    /// call <code>TIFFCodec.registerDecoder(TIFFDecoderMyCompression.class)</code>.
    /// It will be checked if 
    /// <code>decoderClass.newInstance() instanceof Compression</code>
    /// is true and, if so, the class will be added to an internal list.
    /// Whenever a TIFF file is to be decoded, the correct decoder is determined
    /// (each decoder knows about the compression types it supports via the getCompressionTypes method)
    /// and for each tile or strip such a decoder object will be created.
    ///</summary>
    public static void RegisterCompression(Compression newCompression)
    {

      if (newCompression == null)
        return;

      CompressionType[] ctypes = newCompression.CompressionTypes;

      if (ctypes.Length < 1)
        throw new ArgumentException("invalid decoder, decoder supports no compression");

      //int index = 0;
      //while (index < newCompression.CompressionTypes.Length)
      foreach (CompressionType ctype in ctypes)
      {
        _compressions.Add(ctype, newCompression);
      }
    }





    public override BitmapCodecInfo CodecInfo
    {
      get { return _tInfo; }
    }






    //public String FormatName
    //{
    //  get { return "Tagged Image File Format (TIFF)"; }
    //}







    //public String[] MimeTypes
    //{
    //  get { return new String[] { "image/tiff", "image/tif" }; }
    //}






    ///<summary>
    /// Sets the byte order to the argument.
    /// The byte order in a TIFF file is either {@link #BYTE_ORDER_INTEL} or
    /// {@link #BYTE_ORDER_MOTOROLA}.
    /// @param newByteOrder the new byte order to be set
    /// @throws ArgumentException if the argument is not one of the above
    ///  mentioned constants
    ///</summary>
    public TiffDataEndian Endian
    {
      get { return _endian; }
    }








    internal TiffReader TiffReader
    {
      get { return _tiffReader; }
    }





    //private bool _loadBitonalAsPaletted8bpp = true;


    //public bool LoadBitonalAsPaletted8bpp
    //{
    //  get { return _loadBitonalAsPaletted8bpp; }
    //  set 
    //  {
    //    _loadBitonalAsPaletted8bpp = value;
    //    // reread image frames
    //    readImageFrames();
    //  }
    //}


    #endregion public members


    #region private members


    /**
     * Attempts to load an image from a file in the TIFF format.
     * Some options can be given to this codec before the call
     * to this load method.
     * <ul>
     * <li>You must provide a {@link java.io.RandomAccessFile} using
     *   {@link #setInput(java.io.RandomAccessFile)}.</li>
     * <li>If there is more than one image in the input file, you
     *   can make the codec load it by calling {@link #setImageIndex(int)}.
     *   The argument is the index of the image, the first being <code>0</code>,
     *   the second <code>1</code> and so on. The default is <code>0</code>.</li>
     * </ul>
     */
    private void readTiffImageFile(Stream rawStream)
    {
      //read only basic tiff header
      _tiffReader = readHeader(rawStream);

      // save position of first file ifd
      _firstIfdOffset = _tiffReader.ReadInt32();

      // process frames
      readImageFrames();
    }





    /**
     * Reads the first eight bytes from the input file, checks if this is a 
     * valid TIFF file and stores byte order and offset of the first image
     * file directory.
     * @throws IOException if there were reading errors
     * @throws WrongFileFormatException if this is not a valid TIFF file
     */
    protected TiffReader readHeader(Stream tiffStream)
    {

      // the argument to tiffStream.seek must be changed to a variable in the future for
      // this codec to be used to read EXIF information from JPEGs;
      // for some reason, TIFF was chosen for that
      tiffStream.Seek(0, SeekOrigin.Begin);
      // note: this is the only place where we use tiffStream.readInt()
      // directly; afterwards, the detected byte order
      // is regarded via this class' methods readInt() and readShort()
      // methods

      BinaryReader tiffReader = new BinaryReader(tiffStream);

      try
      {
        _endian = (TiffDataEndian)tiffReader.ReadInt16(); //read bytes [0-1], [0-1] endian
      }
      catch
      {
        throw new InvalidFileStructureException("Not a TIFF file (does not " +
          "begin with II or MM).");
      }

      //try tiff magic number
      int magic = EndianConverter.ToInt16(tiffReader.ReadInt16(), _endian);

      if (magic != 42 && magic != 43)
        throw new InvalidFileStructureException("Not a TIFF file (does not " +
          "contain tiff magic number (42 or 43)).");
      else if (magic == 43)
        throw new InvalidFileStructureException("Unsupported TIFF file ( " +
          "big tiff is not suported by this codec).");

      // create and return special endian-sensitive reader
      return new TiffReader(tiffStream, _endian);

    }



    
    /// <summary>
    /// creates all tiff frames
    /// </summary>
    protected void readImageFrames()
    {
      // prepare collection for frames
      List<BitmapFrame> bfs = new List<BitmapFrame>();
      //List<BlockBitmapSource> bfs = new List<BlockBitmapSource>();

      // seek to first ifd
      _tiffReader.BaseStream.Seek(_firstIfdOffset, SeekOrigin.Begin);

      // try load first bitmap from current stream pos
      BlockBitmapSource bs = BlockBitmapSource.Create(_tiffReader, _createOptions, _cacheOption);
      bfs.Add(BitmapFrame.Create(bs));

      while (bs.TiffMetadata.Ifd.NextTableOffset > 0)
      {
        _tiffReader.BaseStream.Seek(bs.TiffMetadata.Ifd.NextTableOffset, SeekOrigin.Begin);
        bs = BlockBitmapSource.Create(_tiffReader, _createOptions ,_cacheOption);
        bfs.Add(BitmapFrame.Create(bs));
      }

      _frames = new System.Collections.ObjectModel.ReadOnlyCollection<BitmapFrame>(bfs);
    }


    #endregion private members


  }
}
